import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter, skip, map, distinctUntilChanged } from 'rxjs/operators';

export type MenuRouterHandlerType = 'normal' | 'route';

@Injectable()
export class MenuRouterHandler {

  /** Observe current url. */
  urlObservable: Observable<string>;

  constructor(
    private router: Router,
  ) {
    this.urlObservable = this.router.events.pipe(
      filter((event) => event instanceof NavigationEnd),
      map((event: NavigationEnd) => event.url),
    );
  }

  /** Find current active url. */
  findActive(urls: string[]): string {
    return urls.find((url) => this.router.isActive(url, false));
  }

  /** Navigate to url. */
  nav(url: string) {
    this.router.navigateByUrl(url);
  }
}

@Injectable()
export class MenuStateService {
  /** Store opened submenus. */
  submenuOpens: {[key: string]: boolean} = {};

  /** Selected menu key observable. */
  selectedKeyObservable: Observable<string>;

  private handler: MenuRouterHandlerType = 'normal';

  private itemKeys: string[] = [];

  private submenuKeys: {[key: string]: string[]} = {};

  private selectedKeySubject = new BehaviorSubject<string>(null);

  constructor(
    private routerHandler: MenuRouterHandler,
  ) {
    // 观察对象有初始化null，skip第一个结果
    this.selectedKeyObservable = this.selectedKeySubject.asObservable().pipe(
      skip(1),
      distinctUntilChanged(),
    );
  }

  /** Init menu state handler. */
  init(handler: MenuRouterHandlerType, selectedKey: string) {
    this.initHandler(handler);
    if (selectedKey) {
      this.select(selectedKey);
    }
    if (this.handler === 'route') {
      this.openSelectedMenuItem();
    }
  }

  /** Select menu by key. */
  select(itemKey: string) {
    if (this.handler === 'route') {
      this.routerHandler.nav(itemKey);
    } else {
      this.updateSelectedKey(itemKey);
    }
  }

  /** Is menu key active. */
  isActive(itemKey: string) {
    const selectedKey = this.selectedKeySubject.getValue();
    return selectedKey === itemKey;
  }

  /** Is submenu key active. */
  isSubmenuActive(submenuKey: string) {
    const keys = this.submenuKeys[submenuKey] || [];
    const selectedKey = this.selectedKeySubject.getValue();
    return keys.indexOf(selectedKey) !== -1;
  }

  /** Register menu by its key. */
  register(itemKey: string, submenuKey?: string) {
    this.itemKeys.push(itemKey);
    if (submenuKey) {
      this.submenuKeys[submenuKey] = this.submenuKeys[submenuKey];
      this.submenuKeys[submenuKey].push(itemKey);
    }
    this.openSelectedMenuItem();
  }

  /** Unregister menu by its key. */
  unregister(itemKey: string, submenuKey?: string) {
    const idx = this.itemKeys.indexOf(itemKey);
    this.itemKeys.splice(idx, 1);
    if (submenuKey) {
      const sIdx = this.submenuKeys[submenuKey].indexOf(itemKey);
      this.submenuKeys[submenuKey].splice(sIdx, 1);
    }
  }

  /** Register submeny by its key. */
  registerSubmenu(submenuKey: string) {
    this.submenuKeys[submenuKey] = [];
  }

  private initHandler(handler: MenuRouterHandlerType) {
    this.handler = handler;
    if (this.handler === 'route') {
      this.routerHandler.urlObservable.subscribe((url) => {
        this.openSelectedMenuItem();
      });
    }
  }

  private openSelectedMenuItem() {
    const selectedItemKey = this.routerHandler.findActive(this.itemKeys);
    const selectedSubKey = this.routerHandler.findActive(Object.keys(this.submenuKeys));
    if (selectedItemKey) {
      this.updateSelectedKey(selectedItemKey);
      const submenuKey = Object.keys(this.submenuOpens).find((key) => this.isSubmenuActive(key));
      if (submenuKey) {
        this.submenuOpens[submenuKey] = true;
      }
    } else if (selectedSubKey) {
      this.updateSelectedKey(selectedSubKey);
    }
  }

  private updateSelectedKey(key: string) {
    this.selectedKeySubject.next(key);
  }
}
